#include <glib.h>

typedef struct {
    GSource source;
    gint interval;
    gint timeout;
} CounterSource;

static gint counter = 0;
static gint64 last_counter_update = 0;

static gboolean counter_prepare(GSource *source, gint* timeout)
{
    CounterSource *cs = (CounterSource*) source;
    gint64 now;

    now = g_source_get_time(source);
    if(now != last_counter_update)
    {
        last_counter_update = now;
        counter+=1;
        // g_print(",");
    }

    *timeout = 1;           // 1ms

    //g_print("counter_prepare: %d, %d\n", counter, cs->timeout);
    g_print((counter > cs->timeout) ?"x":".");

    return counter > cs->timeout;       //true, 表明已经ready, 不会再poll, 直接dispatch
}

static gboolean counter_dispatch(GSource *source, GSourceFunc callback, gpointer data)
{
    CounterSource *cs = (CounterSource*) source;
    gboolean again;

    again = callback(data);

    if(again)
    {
        g_print("%d,%d\n", counter, cs->interval);
        cs->timeout = counter + cs->interval;
    }

    return again;       //true, continue; false, remove
}

static GSourceFuncs counter_source_funcs={
    counter_prepare,
    NULL,
    counter_dispatch,
    NULL,
    NULL,NULL
};

static GSource* counter_source_new(gint interval)
{
    GSource *src = g_source_new(&counter_source_funcs, sizeof(CounterSource));
    CounterSource *cs = (CounterSource*) src;

    cs->interval = interval;
    cs->timeout = counter + interval;
    
    return src;
}


static gint a,b,c;

static gboolean quit_loop_inner(gpointer data)
{
    g_print("quit_loop_inner ....\n");
    GMainLoop *loop = (GMainLoop*) data;

    g_main_loop_quit(loop);

    return G_SOURCE_REMOVE;
}

static gboolean quit_loop(gpointer data)
{
    g_print("quit_loop ....\n");
    GMainLoop *loop = (GMainLoop*) data;

    g_main_loop_quit(loop);

    return G_SOURCE_REMOVE;
}

static gboolean run_inner_loop(gpointer data)
{
    GMainContext *ctx = (GMainContext*) data;
    GMainLoop *loop;
    GSource *timeout;

    a++;
    
    g_print("run_inner_loop...\n");

    loop = g_main_loop_new(ctx, FALSE);     //内嵌的loop
    
    timeout = g_timeout_source_new(100);
    g_source_set_callback(timeout, quit_loop_inner, loop, NULL);
    g_source_attach(timeout, ctx);

    g_main_loop_run(loop);

    g_print("run_inner_loop stop\n");

    g_source_destroy(timeout);
    g_source_unref(timeout);

    g_main_loop_unref(loop);

    g_print("run_inner_loop over\n");

    return G_SOURCE_CONTINUE;
}

static gboolean inc_count(gpointer data)
{
    int *i = data;
    (*i)++;
    if(data == &b)
        g_print("b");
    else
        g_print("c");
    return G_SOURCE_CONTINUE;
}

// 展示child gsource的使用

void show_child_source(void)
{
    GMainContext *ctx;
    GMainLoop *loop;
    GSource *parent, *child_b, *child_c, *end;

    ctx = g_main_context_new();
    loop = g_main_loop_new(ctx, FALSE);

    parent = counter_source_new(2000);
    g_source_set_callback(parent, run_inner_loop, ctx, NULL);
    // g_source_set_priority (parent, G_PRIORITY_LOW);
    g_source_attach(parent, ctx);

    child_b = counter_source_new(250);
    g_source_set_callback(child_b, inc_count, &b, NULL);
    g_source_add_child_source (parent, child_b);

    child_c = counter_source_new(330);
    g_source_set_callback(child_c, inc_count, &c, NULL);
    g_source_set_priority (child_c, G_PRIORITY_HIGH);    
    g_source_add_child_source (parent, child_c);

    //end = g_timeout_source_new(1050);
    end = counter_source_new(1050);
    g_source_set_callback(end, quit_loop, loop, NULL);
    g_source_attach(end, ctx);
    g_source_unref(end);

    g_main_loop_run(loop);

    g_print("a=%d, b=%d, c=%d\n", a, b, c);

    /* The parent source's own timeout will never trigger, so "a" will
    * only get incremented when "b" or "c" does. And when timeouts get
    * blocked, they still wait the full interval next time rather than
    * "catching up". So the timing is:
    *
    *  250 - b++ -> a++, run_inner_loop		//zf, child is ready , so parent is ready
    *  330 - (c is blocked)			//zf, as inner loop is running. child triggered会导致parent dispatch
    *  350 - inner_loop ends
    *  350 - c++ belatedly -> a++, run_inner_loop
    *  450 - inner loop ends
    *  500 - b++ -> a++, run_inner_loop
    *  600 - inner_loop ends
    *  680 - c++ -> a++, run_inner_loop
    *  750 - (b is blocked)
    *  780 - inner loop ends
    *  780 - b++ belatedly -> a++, run_inner_loop
    *  880 - inner loop ends
    * 1010 - c++ -> a++, run_inner_loop
    * 1030 - (b is blocked)
    * 1050 - end runs, quits outer loop, which has no effect yet
    * 1110 - inner loop ends, a returns, outer loop exits
    */

    g_assert_cmpint (a, ==, 6);
    g_assert_cmpint (b, ==, 3);
    g_assert_cmpint (c, ==, 3);

    g_source_destroy(parent);
    g_source_unref(parent);
    g_source_unref(child_b);
    g_source_unref(child_c);

    g_main_loop_unref(loop);
    g_main_context_unref(ctx);
}

int main(int argc, char* argv[])
{
    g_print("a05_gsource start....\n");

    show_child_source();

    g_print("a05_gsource over.\n");
    return 0;
}